// !!!!! Do not use in release mode. Just a native inject fake wrapper for test spider. !!!!!
// !!!!! Do not use in release mode. Just a native inject fake wrapper for test spider. !!!!!
// !!!!! Do not use in release mode. Just a native inject fake wrapper for test spider. !!!!!
import axios from 'axios'
import crypto from 'crypto'
import https from 'https'
// import fs from 'node:fs'
import qs from 'qs'
import { Uri, _ } from './cat.js'

if (!String.prototype.replaceAll) {
  String.prototype.replaceAll = function (old_str, new_str) {
    let str = this
    while (str.includes(search)) {
      str = str.replace(search, replacement)
    }
    return str
  }
}
const confs = new Map()

const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor

// 创建执行函数
globalThis.AsyncFunction = AsyncFunction
const async_eval = new Function(
  'code',
  `return (async () => {
    console.log("=======evals开始======")
    try {
      // 创建异步函数并执行
      const asyncFn = new AsyncFunction( code);
      return await asyncFn(request);
    } catch (error) {
      console.error('执行请求出错:', error);
      throw error;
    }
    console.log("=======evals结束======")
  })();`,
)
async function evals(str) {
  str = str.replaceAll('request(', 'await request(')
  // console.log(str)
  await async_eval(str)
}
function localGet(storage, key) {
  // console.log(storage)
  let m = confs.get(storage)
  if (!m) {
    return ''
  } else {
    let v = m.get(key)
    if (!v) {
      return ''
    } else {
      return v
    }
  }
}

function localSet(storage, key, value) {
  // 如果 confs 是 Map
  if (!confs.has(storage)) {
    confs.set(storage, new Map())
  }
  confs.get(storage).set(key, value)
  // console.log(confs)
}

/**
 * 封装uni.request为类似axios的风格
 * @param {string} url 请求地址
 * @param {object} opt 配置选项
 * @returns {Promise<{code: number, headers: object, content: any}>}
 */
async function unirequest(url, opt = {}) {
  // addtext('发起请求')
  // 参数处理

  // #ifdef H5
  console.log('H5')

  let o = {}
  o.method = 'POST'
  o.data = {}
  // o.data.url = 'https://httpbin.org/get'
  // console.log(
  // url)
  o.data.url = url
  // o.data.url = encodeURIComponent(url)
  url = '/api/post'
  if (!opt.method) {
    opt.method = 'GET'
  }
  o.data.method = opt.method
  if (!opt.headers) {
    opt.headers = {
      'User-Agent':
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36',
    }
  }
  if (!opt.data) {
    opt.data = {}
  }
  o.data.headers = opt.headers
  o.data.data = opt.data
  opt = o
  // console.log(JSON.stringify(o))

  // #endif
  // console.log(opt)
  const {
    method = 'GET',
    data = null,
    headers = {},
    timeout = 15000,
    postType = null,
    buffer = 0,
  } = opt

  // 处理不同post类型
  let requestData = data
  if (postType === 'form') {
    headers['Content-Type'] = 'application/x-www-form-urlencoded'
    if (data) {
      // 需要使用URLSearchParams或qs.stringify
      requestData = Object.keys(data)
        .map(
          (key) =>
            `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`,
        )
        .join('&')
    }
  }

  // 处理响应类型
  const responseType = buffer === 1 || buffer === 2 ? 'arraybuffer' : 'text'

  try {
    // 发起请求
    const [error, res] = await new Promise((resolve) => {
      const task = uni.request({
        url,
        method: method.toUpperCase(),
        data: requestData,
        header: headers,
        timeout,
        responseType,
        success: (res) => resolve([null, res]),
        fail: (err) => resolve([err, null]),
      })

      // 超时处理（模拟）
      setTimeout(() => {
        task.abort()
        resolve([new Error(`Timeout of ${timeout}ms exceeded`), null])
      }, timeout)
    })

    // console.log(responseType)
    if (error) throw error

    // 处理响应头
    const resHeader = {}
    // #ifdef H5
    let res_go = res
    // console.log(res_go.data)
    // try {
    //   res_go.data = JSON.parse(res_go.data)
    // } catch {}
    // console.log('resgo', res_go.data.headers)
    // res = {}
    res.statusCode = 200

    res.header = res_go.data.headers
    res.data = res_go.data.content
    // #endif

    if (res.header) {
      for (const key in res.header) {
        const val = res.header[key]
        resHeader[key] = Array.isArray(val)
          ? val.length === 1
            ? val[0]
            : val
          : val
      }
    }

    // 处理不同返回类型
    let content = res.data
    // console.log('content-->', content)
    if (buffer === 0) {
      if (typeof content === 'object') {
        try {
          content = JSON.stringify(content)
        } catch (e) {
          // 保持原样
        }
      }
    } else if (buffer === 2) {
      // 转换为base64
      content = uni.arrayBufferToBase64(content)
    }

    return {
      code: res.statusCode,
      headers: resHeader,
      content,
    }
  } catch (error) {
    console.error('Request failed:', error)
    return {
      code: error.statusCode || -1,
      headers: {},
      content: null,
      error: error.message,
    }
  }
}

async function request(url, opt) {
  try {
    var data = opt ? opt.data || null : null
    var postType = opt ? opt.postType || null : null
    var returnBuffer = opt ? opt.buffer || 0 : 0
    var timeout = opt ? opt.timeout || 15000 : 15000

    var headers = opt ? opt.headers || {} : {}
    if (postType == 'form') {
      headers['Content-Type'] = 'application/x-www-form-urlencoded'

      if (data != null) {
        data = qs.stringify(data, { encode: false })
      }
    }
    let respType =
      returnBuffer == 1 || returnBuffer == 2 ? 'arraybuffer' : undefined
    var resp = await axios(url, {
      responseType: respType,
      method: opt ? opt.method || 'get' : 'get',
      headers: headers,
      data: data,
      timeout: timeout,
      // httpsAgent: https.Agent({
      //   rejectUnauthorized: false,
      // }),
    })
    var data = resp.data

    var resHeader = {}
    for (const hks of resp.headers) {
      var v = hks[1]
      resHeader[hks[0]] = Array.isArray(v) ? (v.length == 1 ? v[0] : v) : v
    }

    if (!returnBuffer) {
      if (typeof data === 'object') {
        data = JSON.stringify(data)
      }
    } else if (returnBuffer == 1) {
      return { code: resp.status, headers: resHeader, content: data }
    } else if (returnBuffer == 2) {
      return {
        code: resp.status,
        headers: resHeader,
        content: data.toString('base64'),
      }
    }
    return { code: resp.status, headers: resHeader, content: data }
  } catch (error) {
    console.error(error)
  }
  return { headers: {}, content: '' }
}

function base64EncodeBuf(buff, urlsafe = false) {
  return buff.toString(urlsafe ? 'base64url' : 'base64')
}

function base64Encode(text, urlsafe = false) {
  return base64EncodeBuf(Buffer.from(text, 'utf8'), urlsafe)
}

function base64DecodeBuf(text) {
  return Buffer.from(text, 'base64')
}

function base64Decode(text) {
  return base64DecodeBuf(text).toString('utf8')
}

function md5(text) {
  return crypto
    .createHash('md5')
    .update(Buffer.from(text, 'utf8'))
    .digest('hex')
}

function aes(mode, encrypt, input, inBase64, key, iv, outBase64) {
  if (iv.length == 0) iv = null
  try {
    if (mode.startsWith('AES/CBC')) {
      switch (key.length) {
        case 16:
          mode = 'aes-128-cbc'
          break
        case 32:
          mode = 'aes-256-cbc'
          break
      }
    } else if (mode.startsWith('AES/ECB')) {
      switch (key.length) {
        case 16:
          mode = 'aes-128-ecb'
          break
        case 32:
          mode = 'aes-256-ecb'
          break
      }
    }
    const inBuf = inBase64 ? base64DecodeBuf(input) : Buffer.from(input, 'utf8')
    let keyBuf = Buffer.from(key)
    if (keyBuf.length < 16) keyBuf = Buffer.concat([keyBuf], 16)
    let ivBuf = iv == null ? Buffer.alloc(0) : Buffer.from(iv)
    if (iv != null && ivBuf.length < 16) ivBuf = Buffer.concat([ivBuf], 16)
    const cipher = encrypt
      ? crypto.createCipheriv(mode, keyBuf, ivBuf)
      : crypto.createDecipheriv(mode, keyBuf, ivBuf)
    const outBuf = Buffer.concat([cipher.update(inBuf), cipher.final()])
    return outBase64 ? base64EncodeBuf(outBuf) : outBuf.toString('utf8')
  } catch (error) {
    console.log(error)
  }
  return ''
}

function des(mode, encrypt, input, inBase64, key, iv, outBase64) {
  try {
    if (mode.startsWith('DESede/CBC')) {
      // https://stackoverflow.com/questions/29831300/convert-desede-ecb-nopadding-algorithm-written-in-java-into-nodejs-using-crypto
      switch (key.length) {
        case 16:
          mode = 'des-ede-cbc'
          break
        case 24:
          mode = 'des-ede3-cbc'
          break
      }
    }
    const inBuf = inBase64 ? base64DecodeBuf(input) : Buffer.from(input, 'utf8')
    let keyBuf = Buffer.from(key)
    if (keyBuf.length < 16) keyBuf = Buffer.concat([keyBuf], 16)
    let ivBuf = iv == null ? Buffer.alloc(0) : Buffer.from(iv)
    if (iv != null && ivBuf.length < 8) ivBuf = Buffer.concat([ivBuf], 8)
    const cipher = encrypt
      ? crypto.createCipheriv(mode, keyBuf, ivBuf)
      : crypto.createDecipheriv(mode, keyBuf, ivBuf)
    const outBuf = Buffer.concat([cipher.update(inBuf), cipher.final()])
    return outBase64 ? base64EncodeBuf(outBuf) : outBuf.toString('utf8')
  } catch (error) {
    console.log(error)
  }
  return ''
}

// pkcs8 only
function rsa(mode, pub, encrypt, input, inBase64, key, outBase64) {
  try {
    let pd = undefined
    const keyObj = pub
      ? crypto.createPublicKey(key)
      : crypto.createPrivateKey(key)
    if (
      !keyObj.asymmetricKeyDetails ||
      !keyObj.asymmetricKeyDetails.modulusLength
    )
      return ''
    const moduleLen = keyObj.asymmetricKeyDetails.modulusLength
    let blockLen = moduleLen / 8
    switch (mode) {
      case 'RSA/PKCS1':
        pd = crypto.constants.RSA_PKCS1_PADDING
        blockLen = encrypt ? blockLen - 11 : blockLen
        break
      case 'RSA/None/NoPadding':
        pd = crypto.constants.RSA_NO_PADDING
        break
      case 'RSA/None/OAEPPadding':
        pd = crypto.constants.RSA_PKCS1_OAEP_PADDING
        blockLen = encrypt ? blockLen - 41 : blockLen
        break
      default:
        throw Error('not support ' + mode)
    }
    let inBuf = inBase64 ? base64DecodeBuf(input) : Buffer.from(input, 'utf8')
    let bufIdx = 0
    let outBuf = Buffer.alloc(0)
    while (bufIdx < inBuf.length) {
      const bufEndIdx = Math.min(bufIdx + blockLen, inBuf.length)
      let tmpInBuf = inBuf.subarray(bufIdx, bufEndIdx)
      if (pd == crypto.constants.RSA_NO_PADDING) {
        if (tmpInBuf.length < blockLen) {
          tmpInBuf = Buffer.concat([
            Buffer.alloc(128 - tmpInBuf.length),
            tmpInBuf,
          ])
        }
      }
      let tmpBuf
      if (pub) {
        tmpBuf = encrypt
          ? crypto.publicEncrypt({ key: keyObj, padding: pd }, tmpInBuf)
          : crypto.publicDecrypt({ key: keyObj, padding: pd }, tmpInBuf)
      } else {
        tmpBuf = encrypt
          ? crypto.privateEncrypt({ key: keyObj, padding: pd }, tmpInBuf)
          : crypto.privateDecrypt({ key: keyObj, padding: pd }, tmpInBuf)
      }
      bufIdx = bufEndIdx
      outBuf = Buffer.concat([outBuf, tmpBuf])
    }
    return outBase64 ? base64EncodeBuf(outBuf) : outBuf.toString('utf8')
  } catch (error) {
    console.log(error)
  }
  return ''
}

var charStr = 'abacdefghjklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789'

function randStr(len, withNum) {
  var _str = ''
  let containsNum = withNum === undefined ? true : withNum
  for (var i = 0; i < len; i++) {
    let idx = _.random(
      0,
      containsNum ? charStr.length - 1 : charStr.length - 11,
    )
    _str += charStr[idx]
  }
  return _str
}

globalThis.local = {
  get: function (storage, key) {
    return localGet(storage, key)
  },
  set: function (storage, key, value) {
    return localSet(storage, key, value)
  },
}

globalThis.md5X = md5
globalThis.rsaX = rsa
globalThis.aesX = aes
globalThis.desX = des

globalThis.req = request
// globalThis.unireq = request
globalThis.unireq = unirequest
globalThis.evals = evals

globalThis.url2Proxy = async function (type, url, headers) {
  let hd =
    Object.keys(headers).length == 0
      ? '_'
      : encodeURIComponent(JSON.stringify(headers))
  let uri = new Uri(url)
  let path = uri.path()
  path = path.substring(path.lastIndexOf('/'))
  let ext = path.indexOf('.') >= 0 ? path.substring(path.indexOf('.')) : '.bin'
  return (
    'http://127.0.0.1:13333/up/' +
    randStr(6) +
    '/' +
    type +
    '/' +
    hd +
    '/' +
    encodeURIComponent(url) +
    '/' +
    ext
  )
}

globalThis.js2Proxy = async function (dynamic, siteType, site, url, headers) {
  let hd =
    Object.keys(headers).length == 0
      ? '_'
      : encodeURIComponent(JSON.stringify(headers))
  return (
    (dynamic ? 'js2p://_WEB_/' : 'http://127.0.0.1:13333/jp/') +
    randStr(6) +
    '/' +
    siteType +
    '/' +
    site +
    '/' +
    hd +
    '/' +
    encodeURIComponent(url)
  )
}

export default {}
